package com.biboheart.adapter.adapter.impl;

import com.biboheart.adapter.compiler.ast.Node;
import com.biboheart.adapter.compiler.enums.NodeType;
import com.biboheart.adapter.compiler.token.Token;
import com.biboheart.adapter.support.enums.LogicalEnum;
import com.biboheart.adapter.support.enums.OperatorEnum;
import com.biboheart.adapter.support.exception.ConvertException;
import com.biboheart.adapter.support.utils.*;

import java.lang.reflect.Field;
import java.util.*;

final class Source {
    private final Manager manager;

    public Source(Object input) {
        manager = new Manager(input);
    }

    public Object getValueNode(Node node, Map<String, Integer> listIndex) {
        return calcValue(node, listIndex);
    }

    public Map<String, Integer> getListSize() {
        return manager.getListSizeHash();
    }

    public List<String> getListPathList() {
        return manager.getListPathList();
    }

    private Object calcValue(Node ast, Map<String, Integer> listIndex) {
        if (null == ast) {
            return null;
        }
        if (NodeType.NODE_ROOT.equals(ast.getNodeType())) {
            if (ast.getChildren().isEmpty()) {
                return null;
            }
            ast = ast.getChildren().get(0);
        }
        NodeType nodeType = ast.getNodeType();
        Token token = ast.getToken();
        if (NodeType.NODE_CONSTANT.equals(nodeType)) {
            // 如果是常量，直接取值
            return token.getVal();
        }
        if (NodeType.NODE_VARIABLE.equals(nodeType)) {
            // 遇到变量从输入中取值
            return manager.getValue(null, putIndexToPath(String.valueOf(token.getVal()), listIndex), null);
        }
        List<Node> children = ast.getChildren();
        if (null == children || children.isEmpty()) {
            return null;
        }
        List<Object> objectList = new ArrayList<>();
        for (Node child : children) {
            objectList.add(calcValue(child, listIndex));
        }
        Object value = null;
        if (NodeType.NODE_COMMAND.equals(nodeType)) {
            String instructName = token.getCode();
            Object input = objectList.get(0);
            Object[] arr;
            if (objectList.size() > 1) {
                arr = new Object[objectList.size() - 1];
                for (int i = 1; i < objectList.size(); i ++) {
                    arr[i - 1] = objectList.get(i);
                }
            } else {
                arr = new Object[0];
            }
            value = ActuatorUtils.execute(instructName, input, arr);
        } else if (NodeType.NODE_OPERATOR.equals(nodeType)) {
            Object left = objectList.get(0);
            Object right = objectList.size() > 1 ? objectList.get(1) : null;
            if (LogicalEnum.OPERATOR_LOGICAL_NOT.code().equals(token.getCode())) {
                value = LogicalUtils.execute(LogicalEnum.OPERATOR_LOGICAL_NOT.code(), ObjectTypeUtils.convertType(left, Boolean.class));
            } else if (OperatorEnum.OPERATOR_BIT_INV.code().equals(token.getCode())) {
                value = CalculateUtils.execute(OperatorEnum.OPERATOR_BIT_INV.code(), ObjectTypeUtils.convertType(left, Number.class));
            } else if (LogicalEnum.contains(token.getCode())) {
                value = LogicalUtils.execute(token.getCode(), left, right);
            } else if (OperatorEnum.contains(token.getCode())) {
                value = CalculateUtils.execute(token.getCode(), ObjectTypeUtils.convertType(left, Number.class), ObjectTypeUtils.convertType(right, Number.class));
            }
        }
        return value;
    }

    private String putIndexToPath(String path, Map<String, Integer> listIndex) {
        if (null == path || path.isEmpty() || null == listIndex || listIndex.isEmpty()) {
            return path;
        }
        if ('.' != path.charAt(0)) {
            path = "." + path;
        }
        String[] split = path.split("\\.");
        List<String> pathList = new ArrayList<>();
        List<String> keyList = new ArrayList<>();
        for (String str : split) {
            pathList.add(str);
            keyList.add(str);
            String key = String.join(".", keyList);
            if (listIndex.containsKey(key)) {
                pathList.add(String.valueOf(listIndex.get(key)));
            }
        }
        return String.join(".", pathList);
    }

    private static class Manager {
        private final Object input;
        private final Map<String, Integer> listSizeHash = new HashMap<>();
        private final List<String> listPathList = new ArrayList<>();

        private Manager(Object input) {
            this.input = input;
            scanList(input, null, null);
        }

        private Map<String, Integer> getListSizeHash() {
            return listSizeHash;
        }

        private List<String> getListPathList() {
            return listPathList;
        }

        public Object getValue(Object obj, String pathStr, List<String> path) {
            if (null == path) {
                String[] split = pathStr.split("\\.");
                if (split.length == 0) {
                    return null;
                }
                path = new ArrayList<>(Arrays.asList(split));
            }
            String current = path.remove(0);
            if (null == obj) {
                if (current.isEmpty()) {
                    current = path.remove(0);
                }
                obj = input;
            }
            if (null == pathStr || pathStr.isEmpty()) {
                return obj;
            }
            if (obj instanceof List && !ObjectTypeUtils.isNumeric(current)) {
                throw new ConvertException("转换出错: 取值路径[" + pathStr + "]不匹配");
            }
            if (obj instanceof List) {
                int i = Integer.parseInt(current);
                List<?> list = ObjectTypeUtils.convertType(obj, List.class);
                if (null == list || i >= list.size()) {
                    return null;
                }
                obj = list.get(i);
            } else if (obj instanceof Map) {
                Map<?, ?> map = ObjectTypeUtils.convertType(obj, Map.class);
                obj = map.get(current);
            } else if (!ObjectTypeUtils.isPrimitive(obj)) {
                obj = ObjectUtils.getValue(obj, current);
            } else {
                return obj;
            }
            if (null == obj || path.isEmpty()) {
                return obj;
            }
            return getValue(obj, pathStr, path);
        }

        private void scanList(Object input, String path, String noneIndexPath) {
            if (null == input || ObjectTypeUtils.isPrimitive(input)) {
                return;
            }
            if (null == path) {
                path = "";
                noneIndexPath = "";
            }
            boolean isList = (input instanceof List);
            if (isList) {
                List<?> list = ObjectTypeUtils.convertType(input, List.class);
                listSizeHash.put(path, list.size());
                listPathList.add(noneIndexPath);
                int i = 0;
                for (Object obj : list) {
                    scanList(obj, path + "." + i ++, noneIndexPath);
                }
            } else if (input instanceof Map) {
                Map<?, ?> map = ObjectTypeUtils.convertType(input, Map.class);
                for (Map.Entry<?, ?> entry : map.entrySet()) {
                    String key = String.valueOf(entry.getKey());
                    Object obj = entry.getValue();
                    if (ObjectTypeUtils.isPrimitive(obj)) continue;
                    scanList(obj, path + "." + key, noneIndexPath + "." + key);
                }
            } else {
                Class<?> clazz = input.getClass();
                Field[] fields = clazz.getDeclaredFields();//获得属性
                for (Field field : fields) {
                    try {
                        field.setAccessible(true);
                        Object obj = field.get(input);
                        if (ObjectTypeUtils.isPrimitive(obj)) continue;
                        scanList(obj, path + "." + field.getName(), noneIndexPath + "." + field.getName());
                    } catch (IllegalAccessException ignored) {
                    }
                }
            }
        }
    }
}
